/*
 * PHexGrid.java
 *
 * 2d hexagonal grid of crypts.
 * see Clark Verbrugge's Hex Grids article for details (http://www.sable.mcgill.ca/~clump/hexes.txt):
                Hex X coord 

    \   \   \   \   \   \   \   \   \  
     \   \   \   \   \   \   \   \   \  
      0   1   2   3   4   5   6   7   8     
       / \ / \ / \ / \ / \ / \ / \ / \ / \   
      | O | W |   | A |   |   |   |   |   |  
H      \ / \ / \ / \ / \ / \ / \ / \ / \ / \ 
e   0   | V |   | Z |   |   |   |   |   |   |
x  /   / \ / \ / \ / \ / \ / \ / \ / \ / \ / 
  /   | U |   |   |   |   |   |   |   |   |  
Y      \ / \ / \ / \ / \ / \ / \ / \ / \ / \ 
    1   | B |   |   |   |   |   |   |   |   |
c  /   / \ / \ / \ / \ / \ / \ / \ / \ / \ / 
o /   |   |   |   |   |   |   |   |   |   |  
o      \ / \ / \ / \ / \ / \ / \ / \ / \ / \ 
r   2   |   |   |   |   |   |   |   |   |   |
d  /   / \ / \ / \ / \ / \ / \ / \ / \ / \ / 
  /   |   |   |   |   |   |   |   |   |   |  
       \ / \ / \ / \ / \ / \ / \ / \ / \ / \ 
    3   |   |   |   |   |   |   |   |   |   |
   /     \ / \ / \ / \ / \ / \ / \ / \ / \ / 
  /

 in array space,
 U=(0,2), V=(0,1), W=(1,0), Z=(2,1), A=(3,0), B=(0,3)
*/

package sim;

import java.util.*;
import lib.KnuthRandom;

public class PHexGrid extends PGrid {
    // Class constants
    public final static double HEXTISSUEVERTICALSCALINGFACTOR = 0.75; // (1+Math.sin(Math.toRadians(30)))/2.0
	
    // Data Members
    private int _nXSize,_nYSize;  // dimensions of hex grid in crypts
    private Boolean _bWrapX;  // cyclic boundary on X axis
    private Boolean _bWrapY;  // cyclic boundary on Y axis
    private KnuthRandom _r;
	
    // Constructor - allows caller to specify whether the vertical and horizontal edges wrap aroun
    public PHexGrid(int nXSize, int nYSize, CryptFactory factory, TissueEnvironmentSettings params, boolean bWrapX, boolean bWrapY, int initialRow, int initialColumn, double rowCryptFrequency, double colCryptFrequency, int xClipStart, int xClipStop, int yClipStart, int yClipStop, double randomDensity, long iRandomSeed) {
	super();
	_factory = factory;
	params.setGridSizeX(nXSize);
	params.setGridSizeY(nYSize);
	if (!bWrapX && bWrapY) {  // if boundaries must wrap
	    System.err.println("Error in PHexGrid:PHexGrid: If bWrapY is true, then bWrapX must be true.");
	    System.exit(-1);
	}
	_nXSize = nXSize;
	_nYSize = nYSize;
	_bWrapX = bWrapX;
	_bWrapY = bWrapY;
	_crypts = new Crypt[_nXSize*_nYSize];
	for (int i=0; i<_crypts.length; i++) {
	    if(params.getCryptMode()==TissueEnvironmentSettings.CryptMode.Diversity){
		_crypts[i] = factory.createDiversityCrypt();
	    } else if(params.getCryptMode()==TissueEnvironmentSettings.CryptMode.Space){
		_crypts[i] = factory.createSpaceCrypt();	
	    } else if(params.getCryptMode()==TissueEnvironmentSettings.CryptMode.Real){
		_crypts[i] = factory.createRealCrypt();	
	    } 
	    _crypts[i].setGrid(this);
	    _crypts[i].setIndex(i);
	    _crypts[i].setParams(params);
	}
	// Seed the grid with a user-specified pattern
	// One option is to seed the progenitor population at the LES
	for (int i=0; i<_crypts.length; i++) {
	    _crypts[i].setCloneId(-1); // Mark all crypts as dead
	}
 	// // Seed a few crypts at the bottom LES
	// int pos=_crypts.length-5*_nXSize+(int)(1.5*_nXSize); // in the middle of the 5th to last row 
	// // Seed 5 crypts, 5 crypts apart
	// for(int j=0; j<5; j++){
	//     _crypts[pos].setCloneId(0);	
	//     pos+=5;
	// }			    
	
	// Seed a row of crypts, at initial frequency, 0 - 1
	// if(rowCryptFrequency>0.0){
	//     for(int j=0; j<Math.floor(rowCryptFrequency*_nXSize); j++){
	// 	_crypts[initialRow*_nXSize+Math.floor(j*_nXSize/rowCryptFrequency)].setCloneId(0);
	//     }				    
	// }    

	// // Seed a column of crypts, at initial frequency, 0 - 1
	// if(colCryptFrequency>0.0){
	//     for(int j=0; j<Math.floor(colCryptFrequency*_nYSize); j++){
	// 	_crypts[Math.floor(j*_nYSize/colCryptFrequency)*_nXSize+initialCol].setCloneId(0);
	//     }
	// }

	// Seeding of crypts happen at a pre-defined row and column
	// within the boundaries of a row and column clipping region
	// and within the clipping region, 
	// a given frequency of crypts are spread evenly
	// 

	// Fix later Apr 5
	// System.out.println(rowCryptFrequency);
	// System.out.println(colCryptFrequency);
	// System.out.println(xClipStart);
	// System.out.println(xClipStop);

	// if(rowCryptFrequency>0.0){
	//     int clipSize = xClipStop - xClipStart;
	//     int numCrypts = (int)Math.floor(clipSize*rowCryptFrequency);
	//     double stepSize = clipSize*1.0f/(numCrypts+1);
	//     System.out.println(clipSize);
	//     System.out.println(numCrypts);
	//     System.out.println(stepSize);	    
	//     for(int j=1; j<=numCrypts; j++){
	// 	_crypts[initialRow*_nXSize+(xClipStart+(int)Math.floor(j*stepSize))].setCloneId(0);
	// 	System.out.println(initialRow*_nXSize+(xClipStart+(int)Math.floor(j*stepSize)));
	//     }				    
	// }    

	// // Seed a column of crypts, at initial frequency, 0 - 1
	// if(colCryptFrequency>0.0){
	//     int clipSize = yClipStop - yClipStart;
	//     int numCrypts = (int)Math.floor(clipSize*colCryptFrequency);
	//     double stepSize = clipSize/(numCrypts+1);
	//     for(int j=1; j<=numCrypts; j++){
	// 	_crypts[(yClipStart+(int)Math.floor(j*stepSize))*_nXSize+initialColumn].setCloneId(0);
	//     }
	// }

	// Apr 5 - quick fix fill out the grid for now
	for (int i=0; i<_crypts.length; i++) {
	    _crypts[i].setCloneId(0);
	}

	_r = new KnuthRandom();
	_r.seedRandom(iRandomSeed);

	// Sprinkle randomly, at a agiven density
	if(randomDensity>0.0){
	    int numCrypts = (int)Math.floor(randomDensity*_nXSize*_nYSize);
	    // Sprinkle randomly
	    for(int j=1; j<=numCrypts; j++){
		_crypts[_r.randomInt(_nYSize)*_nXSize+_r.randomInt(_nXSize)].setCloneId(0);
	    }
	}

	// Set neighbors, dead or alive, finally
	for (int i=0; i<_crypts.length; i++) {
	    ArrayList n = getNeighbors(i);
	    for (int k=0; k<n.size(); k++) {
		Integer c = (Integer)n.get(k);
		_crypts[i].addNeighbor(_crypts[c.intValue()]);
	    }
	}
	// Test the seeding
	// for (int i=0; i<_crypts.length; i++) {
	//     System.out.print(_crypts[i].getCloneId());
	//     if(i%_nXSize==0){
	// 	System.out.println();
	//     }
	// }	
    }

    // Constructor - creates tissue with hard (non-cyclic) boundaries
    // public PHexGrid(int nXSize, int nYSize, CryptFactory factory, TissueEnvironmentSettings params) {
    // 	this(nXSize, nYSize, factory, params, false, false);
    // }

    // Copy Constructor
    public PHexGrid( PHexGrid rhs ) {

	// Don't copy the base class's members, rather just init them
	super();
		
	// JTE TODO -- throw exception if rhs._factory is null
	_factory = rhs._factory;
		
	this._nXSize = rhs._nXSize;
	this._nYSize = rhs._nYSize;
	this._bWrapX = rhs._bWrapX;
	this._bWrapY = rhs._bWrapY;
	
	// Copy the crypts element by element (we need to do a deep copy, so arraycopy() won't suffice)
	_crypts = new Crypt[rhs._crypts.length];
	for(int i=0; i<_crypts.length; i++ ) {
			
	    if(rhs._crypts[i].getCryptMode()==TissueEnvironmentSettings.CryptMode.Diversity){
                _crypts[i] = _factory.createDiversityCrypt();
            } else if(rhs._crypts[i].getCryptMode()==TissueEnvironmentSettings.CryptMode.Space){
                _crypts[i] = _factory.createSpaceCrypt();
            } else if(rhs._crypts[i].getCryptMode()==TissueEnvironmentSettings.CryptMode.Real){
                _crypts[i] = _factory.createRealCrypt();
            }
	    _crypts[i]._nVertexIndex = rhs._crypts[i]._nVertexIndex;
	    _crypts[i]._grid = rhs._crypts[i]._grid;				    	    
	}
		
	// Add each crypt's neighbors
	for (int i=0; i<_crypts.length; i++) {
	    _crypts[i]._neighbors = new ArrayList<Crypt>();		
			
	    ArrayList n = getNeighbors(i);
	    for (int k=0; k<n.size(); k++) {
		Integer c = (Integer)n.get(k);
		_crypts[i].addNeighbor(_crypts[c.intValue()]);
	    }
	}			
    }
	
    /* getNeighbors
     * returns coordinates of all crypts touching crypt n
     */
    public ArrayList<Integer> getNeighbors(int n) {
	ArrayList<Integer> neighbors = new ArrayList<Integer>();
	int x = n%_nXSize;
	int y = n/_nXSize;
	if (!_bWrapX && !_bWrapY) {
	    if (y>0) {
		neighbors.add(new Integer(x + (y-1)*_nXSize));
		if (y%2==0) {
		    if (x>0)
                        neighbors.add(new Integer((x-1) + (y-1)*_nXSize));
		} else if (y%2==1 && x+1<_nXSize) 
		    neighbors.add(new Integer((x+1) + (y-1)*_nXSize));
	    }
	    if (x>0)
		neighbors.add(new Integer((x-1) + y*_nXSize));
	    if (x+1<_nXSize)
		neighbors.add(new Integer((x+1) + y*_nXSize));
	    if (y+1<_nYSize) {
		neighbors.add(new Integer(x + (y+1)*_nXSize));
		if (y%2==0) {
		    if (x>0)
                        neighbors.add(new Integer((x-1) + (y+1)*_nXSize));
		} else if (y%2==1 && x+1<_nXSize)
		    neighbors.add(new Integer((x+1) + (y+1)*_nXSize));
	    }
	} else if (_bWrapX && !_bWrapY) {
	    int nextx = (x+1)%_nXSize;
	    int prevx = ((x-1)+_nXSize)%_nXSize;
	    int nexty = (y+1)%_nYSize;
	    int prevy = ((y-1)+_nYSize)%_nYSize;
	    if (y>0) {
		neighbors.add(new Integer(x + prevy*_nXSize));
		if (y%2==0) {
		    neighbors.add(new Integer(prevx + prevy*_nXSize));
		} else if (y%2==1)
		    neighbors.add(new Integer(nextx + prevy*_nXSize));
	    }
	    neighbors.add(new Integer(prevx + y*_nXSize));
	    neighbors.add(new Integer(nextx + y*_nXSize));
	    // Found one bug here, include the "&& nexty!=0" to prevent 
	    // the bottom row of the grid having the top row of the grid as neighbors
	    if (nexty<_nYSize && nexty!=0) {
		//	    if (nexty<_nYSize) {
		neighbors.add(new Integer(x + nexty*_nXSize));
		if (y%2==0) {
		    neighbors.add(new Integer(prevx + nexty*_nXSize));
		} else if (y%2==1)
		    neighbors.add(new Integer(nextx + nexty*_nXSize));
	    }
	} else if (_bWrapX && _bWrapY) {
	    int nextx = (x+1)%_nXSize;
	    int prevx = ((x-1)+_nXSize)%_nXSize;
	    int nexty = (y+1)%_nYSize;
	    int prevy = ((y-1)+_nYSize)%_nYSize;
	    neighbors.add(new Integer(x + prevy*_nXSize));
	    if (y%2==0) {
		neighbors.add(new Integer(prevx + prevy*_nXSize));
	    } else if (y%2==1)
		neighbors.add(new Integer(nextx + prevy*_nXSize));
	    neighbors.add(new Integer(prevx + y*_nXSize));
	    neighbors.add(new Integer(nextx + y*_nXSize));
	    neighbors.add(new Integer(x + nexty*_nXSize));
	    if (y%2==0) {
		neighbors.add(new Integer(prevx + nexty*_nXSize));
	    } else if (y%2==1)
		neighbors.add(new Integer(nextx + nexty*_nXSize));
	}
	return neighbors;
    }

    /* 
     * Sets random neighbors of crypt n, 
     * k neighbors at distance r from crypt n
     */
    public void setRandomCryptNeighborsWithinRadius(int n, int k, int r) {
	k = _crypts[n]._neighbors.size();
	//ArrayList<Integer> neighbors = new ArrayList<Integer>(k);
	//_crypts[n]._neighbors = new ArrayList<Crypt>(k);
	// int _nXSize = getWidth();
	// int _nYSize = getHeight();
	int x = n%_nXSize;
	int y = n/_nXSize;
	int rx, ry, rrx, rry;
	if (!_bWrapX && !_bWrapY) {
	    for(int kk=0;kk<k;kk++){
		do{
		    rrx = _r.randomInt(2*r);
		    rry = _r.randomInt(2*r);		    
		    rx = Math.min(Math.max(x+rrx-r,0),_nXSize-1);		
		    ry = Math.min(Math.max(y+rry-r,0),_nYSize-1);		
		} while (Math.hypot((rx-x),(ry-y))>r);
		//neighbors.add(new Integer(rx+ry*_nXSize));	    
		_crypts[n].setNeighbor(kk,_crypts[rx+ry*_nXSize]);	    
	    }
	} else if (_bWrapX && !_bWrapY) {
	    for(int kk=0;kk<k;kk++){
		do{
		    rrx = _r.randomInt(2*r);
		    rry = _r.randomInt(2*r);		    
		    rx = Math.min(Math.max(x+rrx-r,0),_nXSize-1);		
		    ry = Math.min(Math.max(y+rry-r,0),_nYSize-1);		
		} while (Math.hypot((rx-x),(ry-y))>r && Math.hypot((x+_nXSize-rx),(ry-y))>r);
		//neighbors.add(new Integer(rx+ry*_nXSize));	    
		_crypts[n].setNeighbor(kk,_crypts[rx+ry*_nXSize]);	    
	    }
	} else if (_bWrapX && _bWrapY) {
	    for(int kk=0;kk<k;kk++){
		do{
		    rrx = _r.randomInt(2*r);
		    rry = _r.randomInt(2*r);		    
		    rx = Math.min(Math.max(x+rrx-r,0),_nXSize-1);		
		    ry = Math.min(Math.max(y+rry-r,0),_nYSize-1);		
		} while (Math.hypot((rx-x),(ry-y))>r && 
			 Math.hypot((x+_nXSize-rx),(ry-y))>r && 
			 Math.hypot((x+_nXSize-rx),(y+_nYSize-ry))>r &&
			 Math.hypot((rx-x),(y+_nYSize-ry))>r);
		//neighbors.add(new Integer(rx+ry*_nXSize));	    
		_crypts[n].setNeighbor(kk,_crypts[rx+ry*_nXSize]);	    
	    }
	}

	// for (int kk=0; kk<k; kk++) {
	//     Integer c = (Integer)neighbors.get(kk);
	//     _crypts[n].addNeighbor(_crypts[c.intValue()]);
	// }
    }




	
    public double getMaxX() {
	return (_nXSize*2.0 + 1.0)/2.0;
    }
	
    public double getMaxY() {
	return _nYSize*HEXTISSUEVERTICALSCALINGFACTOR;
    }
	
    public int getWidth() {
	return _nXSize;
    }
	
    public int getHeight() {
	return _nYSize;
    }
	
    public double getX(int n) {
	int x = n%_nXSize;
	int y = n/_nXSize;
	//		if (x+(y/2) >= _nXSize)
	//			x -= _nXSize;
	return x+((y%2)/2.0);
    }
	
    public double getY(int n) {
	int x = n%_nXSize;
	int y = n/_nXSize;
	//		if (x+(y/2) >= _nXSize)
	//			x -= _nXSize;
	return y*HEXTISSUEVERTICALSCALINGFACTOR;
    }

    public Crypt getCryptAtCenter() {
	return getCrypt((_nYSize/2)*_nXSize + _nXSize/2);
    }
}
